package com.emotion.recognition.server;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.imageio.ImageIO;

import com.emotion.recognition.client.ProcessImageService;
import com.emotion.recognition.server.network.NeuralNetwork;
import com.emotion.recognition.shared.Constants;
import com.emotion.recognition.shared.Emotion;
import com.emotion.recognition.shared.MLPConfig;
import com.emotion.recognition.shared.Results;
import com.emotion.recognition.shared.SOMConfig;
import com.google.common.collect.Lists;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class ProcessImageServiceImpl extends RemoteServiceServlet implements ProcessImageService {

    private static int CROP_WIDTH = 25;
    private static int CROP_HEIGHT = 16;

    private List<Double> processImage(int x, int y, int width, int height, File file) {
        List<Double> input = Lists.newArrayList();

        // Open file.
        BufferedImage originalImage;
        try {
            originalImage = ImageIO.read(file);

            // Crop the image, as provided by the user.
            BufferedImage croppedImage = originalImage.getSubimage(x, y, width, height);

            // Now, resize that cropped image to fit 40 x 10
            // (and convert to grayscale at the same time)
            BufferedImage resizedImage = new BufferedImage(CROP_WIDTH, CROP_HEIGHT,
                    BufferedImage.TYPE_BYTE_GRAY);
            Graphics2D g = resizedImage.createGraphics();
            g.drawImage(croppedImage, 0, 0, CROP_WIDTH, CROP_HEIGHT, null);
            g.dispose();

            // Save the cropped AND scaled image.
            File outputfile = new File(Constants.CROPPED_DIRECTORY + "/" + file.getName()
                    + "-cropped.png");
            ImageIO.write(resizedImage, "png", outputfile);

            // Convert the picture into a list of doubles.
            for (int i = 0; i < CROP_WIDTH; ++i) {
                for (int j = 0; j < CROP_HEIGHT; ++j) {
                    Color currentColour = new Color(resizedImage.getRGB(i, j));

                    // resizedImage is already grayscale so just take any of R, G, or B
                    int averageValue = currentColour.getRed();

                    // Covert to range between 0..1
                    input.add(averageValue / 255.0);
                }
            }

            return input;
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    @Override
    public Results processImages(String coordinates, Emotion[] emotions, MLPConfig mlp,
            SOMConfig som) throws IllegalArgumentException {

        System.out.println(mlp);

        List<List<Double>> samples = Lists.newArrayList();
        List<List<Double>> targets = Lists.newArrayList();
        List<List<Double>> testing = Lists.newArrayList();

        // Convert emotion to one hot encoding.
        for (Emotion emotion : emotions) {
            targets.add(emotion.getOneHotEncoded());
        }

        File folder = new File(getServletContext().getRealPath(Constants.TESTING_DIRECTORY));
        File[] testingFiles = folder.listFiles();
        Arrays.sort(testingFiles);

        folder = new File(getServletContext().getRealPath(Constants.TRAINING_DIRECTORY));
        File[] trainingFiles = folder.listFiles();
        Arrays.sort(testingFiles);

        File croppedDir = new File(Constants.CROPPED_DIRECTORY);
        if (!croppedDir.exists()) {
            croppedDir.mkdirs();
        }

        int index = -1;

        for (String set : coordinates.split(";")) {
            String[] coordinate = set.split(",");

            ++index;

            // Convert to ints
            List<Integer> coords = new ArrayList<Integer>();
            for (String s : coordinate) {
                coords.add(Integer.valueOf(s));
            }

            // Get inputs for the single image.
            List<Double> input = processImage(coords.get(0), coords.get(2),
                    coords.get(1) - coords.get(0), coords.get(3) - coords.get(2),
                    index >= trainingFiles.length ? testingFiles[index - trainingFiles.length]
                            : trainingFiles[index]);

            if (input == null) {
                throw new IllegalArgumentException();
            }

            if (index >= trainingFiles.length) {
                testing.add(input);
            } else {
                samples.add(input);
            }
        }

        return NeuralNetwork.runNeuralNetworks(samples, targets, testing, mlp, som);
    }
}
